Pi Coding Agent with Devcontainer, Docker Model Runner and Agentic Compose

Introduction: “Tiny Language Models” and CLI coding agents

So, it is entirely possible to use a very small language model with the Qwen Code CLI. But it’s totally useless because the Qwen Code CLI sends system instructions to the agent with each request, and these system instructions are very long. So, your small model will struggle to digest all of this, it will be slow, and context compression will be triggered very quickly, or you’ll get a context overflow error from the first request:

[API Error: 400 the request exceeds the available context size, try increasing it]

Your only option is to increase your model’s context size, but this will only partially solve the problem. And you’ll have deplorable response times: close to 3 minutes to generate a simple “Hello World” in Go, and 24,000 tokens…

However, if you still want to do this for fun, it’s possible, you can take a look at this repository: qwen-code-devcontainer

With a “good” machine and a more powerful agent like Qwen3-Coder-30B-A3B-Instruct-GGUF it makes more sense, but I don’t have that “beefy” machine.

Anyway, in my quest to find useful use cases for “Tiny Language Models”, I have the choice between coding my own agent CLI, or using an existing agent CLI that’s not too context-hungry, or even one that lets you customize the system instructions sent to the agent.

And that’s when I discovered the Pi Mono project thanks to this article Pi: The Minimal Agent Within OpenClaw by Armin Ronacher.

Pi Mono: a minimalist and customizable coding agent

Pi is a minimalist command-line coding agent, with as little complexity and magic as possible. It’s developed by Mario Zechner. To better understand the design choices and philosophy behind Pi, I recommend reading this article by Mario: What I learned building an opinionated and minimal coding agent.

Here’s a quick summary: pi is a CLI tool that can communicate with LLM engines or AI platforms (Anthropic, OpenAI, Google, etc.) via a unified API (pi-ai). It provides an “agent loop” (pi-agent-core) and a TUI (pi-tui) for a chat/terminal interface.

Mario Zechner wants a tool where he has precise control over the context sent to the model (that works for me too), can inspect all requests/responses, and has a simple session format for post-processing.

The operating mode is simple and effective.

Operating mode as a coding agent

Positioning and architecture choices

✋ There is a mechanism for skills and prompt templates, but that will be the subject of future articles. Today, let’s focus on how to prepare a devcontainer project structure to run Pi Mono in a Docker container.

I think I’ve found the ideal candidate to work with my Tiny Language Models 🎉. So, let’s go!

Objective: running Pi with Docker Model Runner and Devcontainer

First, an important thing to know to run Pi with a small language model is that your model must support tools (function calling), and not all small models support it, or support it poorly. But some stand out from the crowd, like Jan Nano for example. Or if you want to use a more code-oriented model, there’s Qwen2.5-Coder-7B-Instruct-GGUF (don’t try the 3b version, the tools won’t work). We’ll use both. And switch from one to the other depending on our needs.

Configuring Pi in our project

You need to create a .pi folder at the root of your project, with the following structure:

.pi
└── agent
    ├── AGENTS.md
    ├── models.json
    └── settings.json

In terms of file content, we’ll have:

AGENTS.md:

You are an expert in software development. Your name is Bob.

Yes, I know I could have put more thought into it 😂

Next, you need to define a provider in the models.json file so that Pi can communicate with Docker Model Runner:

models.json:

{
  "providers": {
    "dockermodelrunner": {
      "baseUrl": "http://model-runner.docker.internal/engines/v1",
      "api": "openai-completions",
      "apiKey": "hello",
      "models": [
        { "id": "hf.co/qwen/qwen2.5-coder-7b-instruct-gguf:q4_k_m" },
        { "id": "hf.co/menlo/jan-nano-gguf:q4_k_m" }
      ]
    }
  }
}
  • dockermodelrunner is the provider name, you can change it if you want.
  • baseUrl is the URL of your Docker Model Runner instance. We use model-runner.docker.internal because we’re going to run Pi in a Docker container, and this will allow it to communicate with the host and therefore Docker Model Runner.
  • api is the type of API we’re going to use to communicate with Docker Model Runner, in our case, we’re going to use the OpenAI-type completions API because Docker Model Runner exposes an OpenAI-compatible API.
  • apiKey you can put whatever you want, Docker Model Runner doesn’t need it.
  • models is the list of models you want to use with this provider.

Finally, we’ll define our agent’s settings:

settings.json:

{
  "defaultProvider": "dockermodelrunner",
  "defaultModel": "hf.co/qwen/qwen2.5-coder-7b-instruct-gguf:q4_k_m"
}

Now that we’ve configured our agent, we can move on to the next step, which is running Pi in a Docker container and making it communicate with Docker Model Runner.

Dockerizing the project with Devcontainer

Still in the project, at the root, you need to create a .devcontainer folder with the following files:

.devcontainer
├── compose.yml
├── devcontainer.json
└── Dockerfile

Contents of the Dockerfile

Let’s first look at the contents of the Dockerfile. For my project, I chose to use an ubuntu base image and install the tools I needed myself, including the Golang toolchain. You’ll adapt it to your needs, but you should keep in mind that you’ll absolutely need to install NodeJS and npm to be able to install and run Pi, as well as the tools that Pi might need to function (like curl for example). So, here’s the content of my Dockerfile:

FROM --platform=$BUILDPLATFORM ubuntu:22.04

LABEL maintainer="@k33g_org"
ARG TARGETOS
ARG TARGETARCH

ARG GO_VERSION=${GO_VERSION}
ARG USER_NAME=${USER_NAME}

ARG DEBIAN_FRONTEND=noninteractive

ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US.UTF-8
ENV LC_COLLATE=C
ENV LC_CTYPE=en_US.UTF-8

# ------------------------------------
# Install Tools
# ------------------------------------
RUN <<EOF
apt-get update
apt-get install -y curl wget jq git build-essential xz-utils bat software-properties-common sudo sshpass unzip fd-find
ln -s /usr/bin/batcat /usr/bin/bat
ln -s $(which fdfind) /usr/local/bin/fd

apt-get clean autoclean
apt-get autoremove --yes
rm -rf /var/lib/{apt,dpkg,cache,log}/
EOF

# ------------------------------------
# Install Go
# ------------------------------------
RUN <<EOF

wget https://golang.org/dl/go${GO_VERSION}.linux-${TARGETARCH}.tar.gz
tar -xvf go${GO_VERSION}.linux-${TARGETARCH}.tar.gz
mv go /usr/local
rm go${GO_VERSION}.linux-${TARGETARCH}.tar.gz
EOF

# ------------------------------------
# Set Environment Variables for Go
# ------------------------------------
ENV PATH="/usr/local/go/bin:${PATH}"
ENV GOPATH="/home/${USER_NAME}/go"
ENV GOROOT="/usr/local/go"

RUN <<EOF
go version
go install -v golang.org/x/tools/gopls@latest
go install -v github.com/ramya-rao-a/go-outline@latest
go install -v github.com/stamblerre/gocode@v1.0.0
go install -v github.com/mgechev/revive@v1.3.2
EOF

# ------------------------------------
# Install NodeJS
# ------------------------------------
ARG NODE_MAJOR
RUN <<EOF
apt-get update && apt-get install -y ca-certificates curl gnupg
curl -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash -
apt-get install -y nodejs
EOF


# Install pi-coding-agent globally
RUN npm install -g @mariozechner/pi-coding-agent

# ------------------------------------
# Create a new user
# ------------------------------------
# Create new regular user `${USER_NAME}` and disable password and gecos for later
# --gecos explained well here: https://askubuntu.com/a/1195288/635348
RUN adduser --disabled-password --gecos '' ${USER_NAME}

#  Add new user `${USER_NAME}` to sudo group
RUN adduser ${USER_NAME} sudo

# Ensure sudo group users are not asked for a password when using
# sudo command by ammending sudoers file
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

# Set the working directory
WORKDIR /home/${USER_NAME}

# Set the user as the owner of the working directory
RUN chown -R ${USER_NAME}:${USER_NAME} /home/${USER_NAME}

# Switch to the regular user
USER ${USER_NAME}

# Avoid the message about sudo
RUN touch ~/.sudo_as_admin_successful

# ------------------------------------
# Install OhMyBash
# ------------------------------------
RUN <<EOF
bash -c "$(curl -fsSL https://raw.githubusercontent.com/ohmybash/oh-my-bash/master/tools/install.sh)"
EOF

Contents of the compose.yml

Next, we’ll define the contents of the compose.yml file which will allow us to run our Docker container with Pi and make it communicate with Docker Model Runner. Here’s the content of my compose.yml:

services:

  pi-mono-workspace:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        - GO_VERSION=1.25.5
        - NODE_MAJOR=22
        - USER_NAME=${USER}
    network_mode: "host"
    volumes:
      - ../..:/workspaces:cached
      - ../.pi/agent:/home/${USER}/.pi/agent
    command: sleep infinity

    models:
      jan-nano:
        endpoint_var: ENGINE_URL
        model_var: JAN_MODEL_ID

      qwen2.5-coder-7b:
        endpoint_var: ENGINE_URL
        model_var: QWEN_7B_MODEL_ID

models:

  jan-nano:
    model: hf.co/menlo/jan-nano-gguf:q4_k_m
    context_size: 8192

  qwen2.5-coder-7b:
    model: hf.co/qwen/qwen2.5-coder-7b-instruct-gguf:q4_k_m
    context_size: 8192

Defining the models in the compose.yml file is not mandatory, but it ensures they’ll be downloaded and ready to use when the container starts if you haven’t done it manually beforehand.

Contents of the devcontainer.json

This one works for me, feel free to adapt it to your needs, especially regarding the extensions you want to install in your container:

devcontainer.json:

{
	"name": "Pi Mono Workspace",

	"dockerComposeFile": "compose.yml",
	"service": "pi-mono-workspace",
    "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
	"features": {
		"ghcr.io/devcontainers/features/git:1": {
			"version": "latest"
		}
	},
	"customizations": {
		"vscode": {
			"extensions": [
				"pkief.material-icon-theme",
				"pkief.material-product-icons",
			]
		}
	},
    "remoteEnv": {
		"LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}",
    },
   	"mounts": [
        "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind",
		"source=${localEnv:HOME}${localEnv:USERPROFILE}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,consistency=cached",
		"source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh,target=/home/${localEnv:USER}/.ssh,type=bind,consistency=cached"
    ],
	"remoteUser": "${localEnv:USER}",
	// Run commands after the container is created.
	"postCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder} ; sudo chmod 666 /var/run/docker.sock;"
}

There you go, now we have everything we need to start our experiments.

Using Pi in our Devcontainer project

Open the project in VSCode with Devcontainer. The first time, grab yourself a coffee ☕️, the build may take a while. Then in a terminal, run the pi command to start Pi and begin asking questions:

Start by asking its name to verify that the AGENTS.md file is being taken into account:

pi-agent

pi-agent

Then ask it to generate a simple “Hello World” in Go and run it to test that the tools are working correctly:

pi-agent

pi-agent

🎉 ok, for a first contact that’s pretty good. I’ll let you familiarize yourself with Pi and in future articles we’ll see how to go further. Happy coding! 👋🤓

✋ You’ll find all the code from this article in this repository: pi-mono-devcontainer. Feel free to take a look and use it as a base for your own experiments with Pi Mono and Tiny Language Models!

© 2026 k33g Project | Built with Gu10berg

Subscribe: 📡 RSS | ⚛️ Atom